import tkinter as tk
import math
import random
import time
from ctypes import windll, POINTER, c_int
from ctypes.wintypes import HWND, RECT


class SafeTuple:
    @staticmethod
    def get(tpl, index, default=None):
        try:
            return tpl[index] if isinstance(tpl, (list, tuple)) and len(tpl) > index else default
        except:
            return default


class WinMouse:
    @staticmethod
    def get_window_rect(hwnd):
        try:
            rect = RECT()
            windll.user32.GetWindowRect(hwnd, POINTER(RECT)(rect))
            return (rect.left, rect.top, rect.right, rect.bottom)
        except:
            return (0, 0, 0, 0)

    @staticmethod
    def set_cursor_pos(x, y):
        try:
            windll.user32.SetCursorPos(x, y)
        except:
            pass


class Vector3:
    def __init__(self, x=0.0, y=0.0, z=0.0):
        self.x = x
        self.y = y
        self.z = z

    def __add__(self, other):
        return Vector3(self.x + other.x, self.y + other.y, self.z + other.z)

    def __sub__(self, other):
        return Vector3(self.x - other.x, self.y - other.y, self.z - other.z)

    def __mul__(self, scalar):
        return Vector3(self.x * scalar, self.y * scalar, self.z * scalar)

    def dot(self, other):
        return self.x * other.x + self.y * other.y + self.z * other.z

    def cross(self, other):
        return Vector3(
            self.y * other.z - self.z * other.y,
            self.z * other.x - self.x * other.z,
            self.x * other.y - self.y * other.x
        )

    def normalize(self):
        length = math.sqrt(self.x ** 2 + self.y ** 2 + self.z ** 2)
        return self * (1.0 / length) if length > 1e-6 else Vector3(0, 0, -1)


class Matrix4x4:
    @staticmethod
    def perspective(fov, aspect, near, far):
        f = 1.0 / math.tan(math.radians(fov) / 2.0)
        return [
            [f / aspect, 0, 0, 0],
            [0, f, 0, 0],
            [0, 0, (far + near) / (near - far), -1],
            [0, 0, (2.0 * far * near) / (near - far), 0]
        ]

    @staticmethod
    def look_at(eye, target, up):
        front = (target - eye).normalize()
        right = front.cross(up).normalize()
        up = right.cross(front).normalize()
        return [
            [right.x, right.y, right.z, -right.dot(eye)],
            [up.x, up.y, up.z, -up.dot(eye)],
            [-front.x, -front.y, -front.z, front.dot(eye)],
            [0, 0, 0, 1]
        ]

    @staticmethod
    def multiply_vector(matrix, vector):
        x = matrix[0][0] * vector.x + matrix[0][1] * vector.y + matrix[0][2] * vector.z + matrix[0][3]
        y = matrix[1][0] * vector.x + matrix[1][1] * vector.y + matrix[1][2] * vector.z + matrix[1][3]
        z = matrix[2][0] * vector.x + matrix[2][1] * vector.y + matrix[2][2] * vector.z + matrix[2][3]
        w = matrix[3][0] * vector.x + matrix[3][1] * vector.y + matrix[3][2] * vector.z + matrix[3][3]
        return Vector3(x / w, y / w, z / w) if abs(w) > 1e-6 else Vector3(x, y, z)


class Vehicle:
    def __init__(self):
        self.position = Vector3(0, 1.2, 8)
        self.yaw = 0.0
        self.pitch = -5.0
        self.roll = 0.0
        self.keys = {"w": False, "s": False, "a": False, "d": False}
        self.speed = 0.0
        self.max_speed = 1000000.0  # 【优化】降低车速，避免片段生成跟不上
        self.max_reverse_speed = -3.0
        self.acceleration = 0.12  # 【优化】减缓加速，让移动更平滑
        self.brake_strength = 0.3
        self.friction = 0.08
        self.steer_sensitivity = 0.8
        self.steer_speed_factor = 0.3
        self.shake_intensity = 0.015
        self.is_on_grass = False
        self.cross_line_timer = 0
        self.fps = 0
        self.current_speed_kmh = 0

    def update(self, mouse_dx):
        self._update_steering(mouse_dx)
        self._update_physics()
        self._update_road_feedback()
        self.current_speed_kmh = round(self.speed * 3.6, 1)

    def _update_steering(self, mouse_dx):
        steer_amount = mouse_dx * self.steer_sensitivity
        speed_factor = max(0.3, 1.0 - (abs(self.speed) / self.max_speed) * self.steer_speed_factor)
        self.yaw += steer_amount * speed_factor
        if self.keys["a"]:
            self.yaw -= 0.5 * speed_factor
        if self.keys["d"]:
            self.yaw += 0.5 * speed_factor
        self.roll = -self.yaw * 0.03 * (abs(self.speed) / self.max_speed)
        self.roll = max(-2, min(2, self.roll))

    def _update_physics(self):
        if self.keys["w"]:
            if self.speed < self.max_speed:
                grass_resist = 0.6 if self.is_on_grass else 1.0
                self.speed += self.acceleration * grass_resist
            else:
                self.speed = self.max_speed
        elif self.keys["s"]:
            if self.speed > 0.1:
                self.speed -= self.brake_strength
            elif self.speed < -0.1:
                self.speed += self.brake_strength
            else:
                self.speed -= self.acceleration * 0.5
                if self.speed < self.max_reverse_speed:
                    self.speed = self.max_reverse_speed
        else:
            if self.speed > 0.05:
                self.speed -= self.friction * (1.5 if self.is_on_grass else 1.0)
            elif self.speed < -0.05:
                self.speed += self.friction * (1.5 if self.is_on_grass else 1.0)
            else:
                self.speed = 0
        rad_yaw = math.radians(self.yaw)
        move_x = math.sin(rad_yaw) * self.speed * 0.08
        move_z = -math.cos(rad_yaw) * self.speed * 0.08
        self.position.x += move_x
        self.position.z += move_z
        road_half_width = 2.5
        if abs(self.position.x) > road_half_width + 1:
            self.position.x = math.copysign(road_half_width + 1, self.position.x)
            self.speed *= 0.95
        self.is_on_grass = abs(self.position.x) > road_half_width

    def _update_road_feedback(self):
        grass_shake = 0.0
        if self.is_on_grass and abs(self.speed) > 1:
            grass_shake = random.uniform(-self.shake_intensity * 1.5, self.shake_intensity * 1.5)
        line_shake = 0.0
        self.cross_line_timer -= 1
        if self.cross_line_timer <= 0:
            if abs(self.position.x) < 0.3 and not self.is_on_grass and self.speed > 1:
                line_shake = random.uniform(-self.shake_intensity * 2, self.shake_intensity * 2)
                self.cross_line_timer = 60
        total_shake = grass_shake + line_shake
        self.pitch = -5.0 + total_shake

    def get_view_matrix(self):
        front = Vector3(
            math.sin(math.radians(self.yaw)) * math.cos(math.radians(self.pitch)),
            math.sin(math.radians(self.pitch)),
            -math.cos(math.radians(self.yaw)) * math.cos(math.radians(self.pitch))
        ).normalize()
        target = self.position + front * 10
        base_up = Vector3(0, 1, 0)
        if abs(self.roll) > 1e-6:
            right = front.cross(base_up).normalize()
            roll_rad = math.radians(self.roll)
            base_up = base_up * math.cos(roll_rad) + right * math.sin(roll_rad)
        return Matrix4x4.look_at(self.position, target, base_up)

    def set_key_state(self, key, is_pressed):
        if key in self.keys:
            self.keys[key] = is_pressed


class RoadGenerator:
    def __init__(self):
        self.segment_length = 2.0  # 【核心修复1】缩短片段长度，让拼接更密集，减少断层视觉
        self.road_width = 5.0
        self.grass_width = 10.0
        self.max_segments = 35  # 【核心修复2】增加最大片段数，确保视野内始终有足够片段
        self.line_positions = []
        self.segments = self._generate_segments()

    def _generate_segments(self):
        segments = []
        self.line_positions.clear()
        # 生成时确保z值连续且精确（i从0开始，z=-i*segment_length，相邻片段z值无缝衔接）
        for i in range(self.max_segments):
            z = -i * self.segment_length
            segments.append((
                Vector3(-self.road_width / 2, 0, z),  # 左边缘x固定：-2.5
                Vector3(self.road_width / 2, 0, z),  # 右边缘x固定：2.5
                Vector3(-self.road_width / 2 - self.grass_width, 0, z),
                Vector3(self.road_width / 2 + self.grass_width, 0, z)
            ))
            if i % 2 == 0:
                self.line_positions.append(z)
        return segments

    def update(self, camera_z):
        # 【核心修复3】扩大渲染范围（camera_z - 150），避免远处片段被过早删除
        min_z = camera_z - 100
        # 只删除远超出视野的片段（保留更多近处片段，确保拼接连续）
        self.segments = [s for s in self.segments if s[0].z >= min_z - 20]
        self.line_positions = [z for z in self.line_positions if z >= min_z - 20]

        # 【核心修复4】强制补充片段，直到数量达到max_segments，避免片段不足
        while len(self.segments) < self.max_segments:
            # 取最后一个片段的z值，确保新片段z值连续（无间隙）
            last_z = self.segments[-1][0].z if self.segments else 0.0
            new_z = last_z - self.segment_length  # 新片段z值 = 上一段z - 片段长度（无缝衔接）
            self.segments.append((
                Vector3(-self.road_width / 2, 0, new_z),
                Vector3(self.road_width / 2, 0, new_z),
                Vector3(-self.road_width / 2 - self.grass_width, 0, new_z),
                Vector3(self.road_width / 2 + self.grass_width, 0, new_z)
            ))
            # 同步补充分隔线位置
            if len(self.segments) % 2 == 1:
                self.line_positions.append(new_z)


class GrassField:
    def __init__(self, road):
        self.road = road
        self.blades = self._generate_blades()

    def _generate_blades(self):
        blades = []
        for seg in self.road.segments:
            z = seg[0].z
            if z < -120:  # 扩大草叶生成范围，与道路匹配
                continue
            for _ in range(4):
                x = -self.road.road_width / 2 - (1.0 + random.random() * self.road.grass_width * 0.7)
                height = 0.2 + random.random() * 0.35
                blades.append((x, height, z))
            for _ in range(4):
                x = self.road.road_width / 2 + (1.0 + random.random() * self.road.grass_width * 0.7)
                height = 0.2 + random.random() * 0.35
                blades.append((x, height, z))
        return blades

    def update(self):
        self.blades = self._generate_blades()


class GameRenderer:
    def __init__(self, canvas, width, height):
        self.canvas = canvas
        self.width = width
        self.height = height
        self.objects = self._create_render_objects()

    def _create_render_objects(self):
        return {
            # 道路片段数量与RoadGenerator的max_segments一致（35），避免渲染时数量不足
            "road": [self.canvas.create_polygon([0, 0, 0, 0, 0, 0, 0, 0], fill="#4A4A4A") for _ in range(35)],
            "grass_left": [self.canvas.create_polygon([0, 0, 0, 0, 0, 0, 0, 0], fill="#2E8B57") for _ in range(35)],
            "grass_right": [self.canvas.create_polygon([0, 0, 0, 0, 0, 0, 0, 0], fill="#2E8B57") for _ in range(35)],
            "road_line": [self.canvas.create_line([0, 0, 0, 0], fill="#FFFFFF", width=3, dash=(5, 3)) for _ in
                          range(18)],
            "grass_blades": [self.canvas.create_line([0, 0, 0, 0], fill="#32CD32", width=1) for _ in range(280)],
            "speed_text": self.canvas.create_text(780, 520, text="速度: 0.0 km/h", fill="#FFFFFF",
                                                  font=("Arial", 14, "bold"), anchor="ne"),
            "gear_text": self.canvas.create_text(780, 550, text="挡位: 前进", fill="#FFFFFF",
                                                 font=("Arial", 12, "bold"), anchor="ne"),
            "fps_text": self.canvas.create_text(20, 20, text="FPS: 0", fill="#FFFFFF", font=("Arial", 12, "bold"),
                                                anchor="nw"),
            "help_text": self.canvas.create_text(400, 580, text="W=油门 | S=刹车/倒车 | 鼠标=转向 | ESC=退出",
                                                 fill="#FFFFFF", font=("Arial", 11, "bold"), anchor="center")
        }

    def project(self, point, view_matrix, projection_matrix):
        view_point = Matrix4x4.multiply_vector(view_matrix, point)
        # 【优化】只过滤远超出视野的后方点，避免近处片段被误判
        if view_point.z > 5.0:
            return (self.width // 2, self.height // 2)
        proj_point = Matrix4x4.multiply_vector(projection_matrix, view_point)
        # 放宽坐标限制，避免边缘片段被截断
        x = max(-50, min(self.width + 50, (proj_point.x + 1) * self.width / 2))
        y = max(-50, min(self.height + 50, (1 - proj_point.y) * self.height / 2))
        return (int(x), int(y))

    def render(self, vehicle, road, grass, projection):
        view_matrix = vehicle.get_view_matrix()
        vehicle_z = vehicle.position.z
        # 按z值排序（远处→近处），确保渲染顺序正确，无遮挡断层
        sorted_segs = sorted(road.segments, key=lambda s: s[0].z, reverse=True)

        # 【核心修复5】渲染所有可用片段（不再固定取前30个），确保无遗漏
        for seg_idx, current_seg in enumerate(sorted_segs):
            next_idx = seg_idx + 1
            if next_idx >= len(sorted_segs):
                break
            next_seg = sorted_segs[next_idx]

            # 提取顶点（确保相邻片段顶点x/z完全对齐）
            c_left, c_right, c_gl, c_gr = current_seg
            n_left, n_right, n_gl, n_gr = next_seg

            # 投影顶点
            p_c_left = self.project(c_left, view_matrix, projection)
            p_c_right = self.project(c_right, view_matrix, projection)
            p_c_gl = self.project(c_gl, view_matrix, projection)
            p_c_gr = self.project(c_gr, view_matrix, projection)
            p_n_left = self.project(n_left, view_matrix, projection)
            p_n_right = self.project(n_right, view_matrix, projection)
            p_n_gl = self.project(n_gl, view_matrix, projection)
            p_n_gr = self.project(n_gr, view_matrix, projection)

            # 【优化】减缓颜色暗化速度，避免远处片段过暗导致“视觉断层”
            distance = abs(c_left.z - vehicle_z)
            distance_factor = max(0.3, 1.0 - min(1.0, distance / 100))  # 分母从80→100
            grass_color = f"#{int(30 * distance_factor):02x}{int(120 * distance_factor):02x}{int(30 * distance_factor):02x}"
            road_color = f"#{int(60 * distance_factor):02x}{int(60 * distance_factor):02x}{int(60 * distance_factor):02x}"

            # 渲染草地（确保与道路片段数量匹配）
            if seg_idx < len(self.objects["grass_left"]):
                self.canvas.coords(self.objects["grass_left"][seg_idx],
                                   p_c_gl[0], p_c_gl[1], p_n_gl[0], p_n_gl[1],
                                   p_n_left[0], p_n_left[1], p_c_left[0], p_c_left[1])
                self.canvas.itemconfig(self.objects["grass_left"][seg_idx], fill=grass_color)

                self.canvas.coords(self.objects["grass_right"][seg_idx],
                                   p_c_right[0], p_c_right[1], p_n_right[0], p_n_right[1],
                                   p_n_gr[0], p_n_gr[1], p_c_gr[0], p_c_gr[1])
                self.canvas.itemconfig(self.objects["grass_right"][seg_idx], fill=grass_color)

                # 渲染道路（核心：相邻片段顶点拼接无间隙）
                self.canvas.coords(self.objects["road"][seg_idx],
                                   p_c_left[0], p_c_left[1], p_n_left[0], p_n_left[1],
                                   p_n_right[0], p_n_right[1], p_c_right[0], p_c_right[1])
                self.canvas.itemconfig(self.objects["road"][seg_idx], fill=road_color)

            # 渲染分隔线
            if seg_idx % 2 == 0 and seg_idx < len(self.objects["road_line"]):
                c_mid = Vector3(0, 0.01, c_left.z)
                n_mid = Vector3(0, 0.01, n_left.z)
                p_c_mid = self.project(c_mid, view_matrix, projection)
                p_n_mid = self.project(n_mid, view_matrix, projection)
                self.canvas.coords(self.objects["road_line"][seg_idx // 2], p_c_mid[0], p_c_mid[1], p_n_mid[0],
                                   p_n_mid[1])
                self.canvas.itemconfig(self.objects["road_line"][seg_idx // 2], fill="wheat")

        # 渲染草叶
        for i in range(min(len(self.objects["grass_blades"]), len(grass.blades))):
            blade = grass.blades[i]
            x, height, z = blade
            distance = abs(z - vehicle_z)
            if distance > 40:
                self.canvas.itemconfig(self.objects["grass_blades"][i], fill="")
                continue
            green = int(100 + 40 * (1 - min(1.0, distance / 40)))
            if abs(x) < 2.5:
                self.canvas.itemconfig(self.objects["grass_blades"][i], fill="")
                continue
            base = self.project(Vector3(x, 0, z), view_matrix, projection)
            top = self.project(Vector3(x, height, z), view_matrix, projection)
            self.canvas.coords(self.objects["grass_blades"][i], base[0], base[1], top[0], top[1])
            self.canvas.itemconfig(self.objects["grass_blades"][i], fill=f"#{20:02x}{green:02x}{20:02x}")

        # 更新UI
        self.canvas.itemconfig(self.objects["speed_text"], text=f"速度: {vehicle.current_speed_kmh} km/h")
        gear_text = "挡位: 倒车" if vehicle.speed < -0.1 else "挡位: 前进" if vehicle.speed > 0.1 else "挡位: 空挡"
        self.canvas.itemconfig(self.objects["gear_text"], text=gear_text)
        self.canvas.itemconfig(self.objects["fps_text"], text=f"FPS: {vehicle.fps}")
        self.canvas.tag_raise("speed_text")
        self.canvas.tag_raise("gear_text")
        self.canvas.tag_raise("fps_text")
        self.canvas.tag_raise("help_text")


class Game:
    def __init__(self):
        self.width, self.height = 800, 600
        self.running = True
        self.center_x, self.center_y = self.width // 2, self.height // 2
        self.mouse_dx = 0
        self.root = tk.Tk()
        self.root.title("3D第一人称开车模拟（无断层）")
        self.root.geometry(f"{self.width}x{self.height}")
        self.root.resizable(False, False)
        self.root.protocol("WM_DELETE_WINDOW", self.quit)
        self.canvas = tk.Canvas(self.root, bg="#87CEEB")
        self.canvas.pack(fill="both", expand=True)
        self.vehicle = Vehicle()
        self.road = RoadGenerator()
        self.grass = GrassField(self.road)
        self.renderer = GameRenderer(self.canvas, self.width, self.height)
        # 【优化】调整投影参数，扩大视野覆盖范围
        self.projection = Matrix4x4.perspective(68, self.width / self.height, 0.3, 180)  # fov从65→68，far从120→180
        self._bind_keys()
        self.root.config(cursor="none")
        self.canvas.bind("<Motion>", self._on_mouse_move)
        self.last_fps_time = time.time()
        self.frame_count = 0

    def _bind_keys(self):
        self.root.bind("<KeyPress>", self._on_key_press)
        self.root.bind("<KeyRelease>", self._on_key_release)

    def _on_key_press(self, event):
        key = event.keysym.lower()
        if key in ["w", "s", "a", "d"]:
            self.vehicle.set_key_state(key, True)
        if key == "escape":
            self.quit()

    def _on_key_release(self, event):
        key = event.keysym.lower()
        if key in ["w", "s", "a", "d"]:
            self.vehicle.set_key_state(key, False)

    def _on_mouse_move(self, event):
        self.mouse_dx = event.x - self.center_x
        if time.time() - getattr(self, "last_mouse_reset", 0) > 0.015:
            self._reset_mouse()
            self.last_mouse_reset = time.time()

    def _reset_mouse(self):
        hwnd = windll.user32.GetForegroundWindow() if hasattr(windll, "user32") else 0
        if hwnd == self.root.winfo_id():
            left, top = WinMouse.get_window_rect(hwnd)[:2]
            WinMouse.set_cursor_pos(left + self.center_x, top + self.center_y)

    def update(self):
        self.vehicle.update(self.mouse_dx)
        self.mouse_dx = 0
        self.road.update(self.vehicle.position.z)
        self.grass.update()
        now = time.time()
        self.frame_count += 1
        if now - self.last_fps_time >= 1.0:
            self.vehicle.fps = self.frame_count
            self.frame_count = 0
            self.last_fps_time = now

    def render(self):
        self.renderer.render(self.vehicle, self.road, self.grass, self.projection)

    def run(self):
        def loop():
            if self.running:
                self.update()
                self.render()
                self.root.after(16, loop)

        self.root.after(100, self._reset_mouse)
        loop()
        self.root.mainloop()

    def quit(self):
        self.running = False
        self.root.after(100, self.root.destroy)


if __name__ == "__main__":
    try:
        game = Game()
        game.run()
    except Exception as e:
        print(f"运行错误: {e}")
        input("按回车退出...")